//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the \"Software\"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------

local file = vargv[0]
if (type(file) != "instance" || file.getclass() != File)
{
  throw "expected instance of class File"
}

local arguments = _OPTIONS["arguments"]

file.source_includes.append("gmock/gmock.h")
file.source_includes.append("sqbind/sqbBind.h")
file.source_includes.append("../fixtures/SquirrelFixture.h")
file.source_includes.append(format("../mocks/autogen/MockFunction%d.h", arguments))

enum ReturnTypes
{
  kReturn,
  kReturnVoid,
  kReturnInvalid,
}

enum BindTypes
{
  kBindFunction,
  kBindClassFunction,
  kBindSingletonFunction,
}

//----------------------------------------------------------------------------------------------------------------------
local add_function_test = function(file, argument_count, bind_type, return_type)
{
  local suite_name = null
  switch (bind_type)
  {
  case BindTypes.kBindFunction:
    suite_name = "BindFunctionTest"
    break
  case BindTypes.kBindClassFunction:
    suite_name = "BindClassFunctionTest"
    break
  case BindTypes.kBindSingletonFunction:
    suite_name = "BindSingletonFunctionTest"
    break
  default:
    assert(false)
    break
  }
  local test = null
  local function_name = null
  switch (return_type)
  {
  case ReturnTypes.kReturn:
    local test_name_format = (argument_count != 1) ? "Test%dParameters" : "Test%dParameter"
    test = TestFixture(suite_name, format(test_name_format, argument_count))
    function_name = format("Function%d", argument_count)
    break
  case ReturnTypes.kReturnVoid:
    local test_name_format = (argument_count != 1) ? "Test%dParametersReturnVoid" : "Test%dParameterReturnVoid"
    test = TestFixture(suite_name, format(test_name_format, argument_count))
    function_name = format("VoidFunction%d", argument_count)
    break
  case ReturnTypes.kReturnInvalid:
    local test_name_format = (argument_count != 1) ? "Test%dParametersReturnInvalid" : "Test%dParameterReturnInvalid"
    test = TestFixture(suite_name, format(test_name_format, argument_count))
    function_name = format("InvalidFunction%d", argument_count)
    break
  default:
    assert(false)
    break
  }

  test.body.add_line("::testing::StrictMock<MockFunction%d> mock;", argument_count)
  test.body.add_line("MockFunction%d::m_instance = &mock;", argument_count)
  test.body.add_new_line()

  if (argument_count > 0 || return_type == ReturnTypes.kReturn)
  {
    test.body.add_line("sq_pushroottable(m_vm);")
    test.body.add_line("sqb::Bind::BindClass<BoundClass, sqb::NoBaseClass>(m_vm, -1, _SC(\"BoundClass\"));")
    test.body.add_line("sqb::Bind::BindClass<OtherBoundClass, sqb::NoBaseClass>(m_vm, -1, _SC(\"OtherBoundClass\"));")
    test.body.add_line("sq_poptop(m_vm);")
    test.body.add_new_line()
  }

  local argument_string = ""

  if (argument_count > 0)
  {
    if (return_type != ReturnTypes.kReturnInvalid)
    {
      test.body.add_line("OtherBoundClass invalidParameter;")
    }
    test.body.add_line("BoundClass parameter0(0);")
    argument_string = "parameter0"

    for (local i = 1; i < argument_count; ++i)
    {
      test.body.add_line("BoundClass parameter%d(%d);", i, i)
      argument_string = format("%s, parameter%d",argument_string, i)
    }

    test.body.add_new_line()
  }

  switch (return_type)
  {
  case ReturnTypes.kReturn:
    test.body.add_line("BoundClass expected_result(0xffffffff);")

    test.body.add_line("EXPECT_CALL(mock, ClassFunction%d(%s))", argument_count, argument_string)
    if (bind_type == BindTypes.kBindClassFunction ||
        bind_type == BindTypes.kBindSingletonFunction)
    {
      test.body.add_line("  .WillOnce(::testing::Return(expected_result));")
      test.body.add_line("EXPECT_CALL(mock, ConstClassFunction%d(%s))", argument_count, argument_string)
      test.body.add_line("  .WillOnce(::testing::Return(expected_result));")
    }
    else
    {
      test.body.add_line("  .Times(2)")
      test.body.add_line("  .WillRepeatedly(::testing::Return(expected_result));")
    }
    break
  case ReturnTypes.kReturnVoid:
    test.body.add_line("EXPECT_CALL(mock, ClassVoidFunction%d(%s))", argument_count, argument_string)

    if (bind_type == BindTypes.kBindClassFunction ||
        bind_type == BindTypes.kBindSingletonFunction)
    {
      test.body.add_line("  .Times(1);")
      test.body.add_line("EXPECT_CALL(mock, ConstClassVoidFunction%d(%s))", argument_count, argument_string)
      test.body.add_line("  .Times(1);")
    }
    else
    {
      test.body.add_line("  .Times(2);")
    }
    break
  case ReturnTypes.kReturnInvalid:
    test.body.add_line("InvalidStackUtilsClass expected_result;")
    test.body.add_line("EXPECT_CALL(mock, ClassInvalidFunction%d(%s))", argument_count, argument_string)

    if (bind_type == BindTypes.kBindClassFunction ||
        bind_type == BindTypes.kBindSingletonFunction)
    {
      test.body.add_line("  .WillOnce(::testing::Return(expected_result));")
      test.body.add_line("EXPECT_CALL(mock, ConstClassInvalidFunction%d(%s))", argument_count, argument_string)
      test.body.add_line("  .WillOnce(::testing::Return(expected_result));")
    }
    else
    {
      test.body.add_line("  .Times(2)")
      test.body.add_line("  .WillRepeatedly(::testing::Return(expected_result));")
    }
    break
  default:
    break
  }

  test.body.add_new_line()

  test.body.add_line("// bind the objects required for calling the function")
  test.body.add_line("//")
  test.body.add_line("sq_pushroottable(m_vm);")
  test.body.add_new_line()

  switch (bind_type)
  {
  case BindTypes.kBindFunction:
    test.body.add_line(
      "EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, -1, &MockFunction%d::%s, _SC(\"%s\")));",
      argument_count,
      function_name,
      function_name)

    test.body.add_new_line()

    test.body.add_line("sq_pushstring(m_vm, _SC(\"Mock\"), -1);")
    test.body.add_line("EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));")
    test.body.add_line(
      "EXPECT_TRUE(sqb::Bind::BindFunction(m_vm, 3, &MockFunction%d::%s, _SC(\"%s\")));",
      argument_count,
      function_name,
      function_name)
    test.body.add_line("EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));")
    test.body.add_new_line()
    break
  case BindTypes.kBindClassFunction:
  case BindTypes.kBindSingletonFunction:
    test.body.add_line("// bind the mock class")
    test.body.add_line("//")

    if (bind_type == BindTypes.kBindSingletonFunction)
    {
      test.body.add_line("sq_pushstring(m_vm, _SC(\"Mock\"), -1);")
    }

    test.body.add_line("EXPECT_SQ_SUCCEEDED(m_vm, sq_newclass(m_vm, SQFalse));")

    if (bind_type == BindTypes.kBindClassFunction)
    {
      test.body.add_line("EXPECT_SQ_SUCCEEDED(m_vm, sq_settypetag(m_vm, -1, sqb::ClassTypeTag<MockFunction%d>::Get()));", argument_count)
      test.body.add_line("HSQOBJECT class_object;")
      test.body.add_line("sq_getstackobj(m_vm, -1, &class_object);")
      test.body.add_line("sqb::ClassTypeTag<MockFunction%d>::Get()->SetClassObject(m_vm, class_object);", argument_count)

      test.body.add_line(
        "EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunction%d>(m_vm, -1, &MockFunction%d::Class%s, _SC(\"Class%s\")));",
        argument_count,
        argument_count,
        function_name,
        function_name)
      test.body.add_line(
        "EXPECT_TRUE(sqb::Bind::BindClassFunction<MockFunction%d>(m_vm, 2, &MockFunction%d::ConstClass%s, _SC(\"ConstClass%s\")));",
        argument_count,
        argument_count,
        function_name,
        function_name)
      test.body.add_line("sq_poptop(m_vm);")
      test.body.add_new_line()

      test.body.add_line("EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, static_cast<MockFunction%d *>(&mock), _SC(\"mock\")));", argument_count)
      test.body.add_new_line()
    }
    else
    {
      test.body.add_line(
        "EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunction%d>(m_vm, -1, &mock, &MockFunction%d::Class%s, _SC(\"Class%s\")));",
        argument_count,
        argument_count,
        function_name,
        function_name)
      test.body.add_line(
        "EXPECT_TRUE(sqb::Bind::BindSingletonFunction<MockFunction%d>(m_vm, 1, &mock, &MockFunction%d::ConstClass%s, _SC(\"ConstClass%s\")));",
        argument_count,
        argument_count,
        function_name,
        function_name)

      test.body.add_line("EXPECT_SQ_SUCCEEDED(m_vm, sq_rawset(m_vm, -3));")
      test.body.add_new_line()
    }
    break
  }

  if (argument_count > 0)
  {
    if (return_type != ReturnTypes.kReturnInvalid)
    {
      test.body.add_line("EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &invalidParameter, _SC(\"invalidParameter\")));")
    }

    for (local i = 0; i < argument_count; ++i)
    {
      test.body.add_line("EXPECT_TRUE(sqb::Bind::BindVariable(m_vm, -1, &parameter%d, _SC(\"parameter%d\")));", i, i)
    }
  }

  test.body.add_line("sq_poptop(m_vm);")

  test.body.add_new_line()

  switch (return_type)
  {
  case ReturnTypes.kReturn:
    switch (bind_type)
    {
    case BindTypes.kBindFunction:
      test.body.add_line("BoundClass actual_result = CompileAndCallReturnResult<BoundClass>(\"return %s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      test.body.add_line("actual_result = CompileAndCallReturnResult<BoundClass>(\"return Mock.%s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      break
    case BindTypes.kBindClassFunction:
      test.body.add_line("BoundClass actual_result = CompileAndCallReturnResult<BoundClass>(\"return mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      test.body.add_line("actual_result = CompileAndCallReturnResult<BoundClass>(\"return mock.ConstClass%s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      break
    case BindTypes.kBindSingletonFunction:
      test.body.add_line("BoundClass actual_result = CompileAndCallReturnResult<BoundClass>(\"return Mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      test.body.add_line("actual_result = CompileAndCallReturnResult<BoundClass>(\"return ConstClass%s(%s)\");", function_name, argument_string)
      test.body.add_line("EXPECT_EQ(expected_result, actual_result);");
      break
    }
    break
  case ReturnTypes.kReturnVoid:
    switch (bind_type)
    {
    case BindTypes.kBindFunction:
      test.body.add_line("CompileAndSucceedCall(\"%s(%s)\");", function_name, argument_string)
      test.body.add_line("CompileAndSucceedCall(\"Mock.%s(%s)\");", function_name, argument_string)
      break
    case BindTypes.kBindClassFunction:
      test.body.add_line("CompileAndSucceedCall(\"mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("CompileAndSucceedCall(\"mock.ConstClass%s(%s)\");", function_name, argument_string)
      break
    case BindTypes.kBindSingletonFunction:
      test.body.add_line("CompileAndSucceedCall(\"Mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("CompileAndSucceedCall(\"ConstClass%s(%s)\");", function_name, argument_string)
      break
    }
    break
  case ReturnTypes.kReturnInvalid:
    switch (bind_type)
    {
    case BindTypes.kBindFunction:
      test.body.add_line("CompileAndFailCall(\"%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      test.body.add_line("CompileAndFailCall(\"Mock.%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      break
    case BindTypes.kBindClassFunction:
      test.body.add_line("CompileAndFailCall(\"mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      test.body.add_line("CompileAndFailCall(\"mock.ConstClass%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      break
    case BindTypes.kBindSingletonFunction:
      test.body.add_line("CompileAndFailCall(\"Mock.Class%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      test.body.add_line("CompileAndFailCall(\"ConstClass%s(%s)\");", function_name, argument_string)
      test.body.add_line("CheckErrorString(\"error returning object of type 'InvalidStackUtilsClass'\");");
      break
    }
    break
  default:
    assert(false)
    break
  }

  if (return_type != ReturnTypes.kReturnInvalid)
  {
    for (local i = 0; i != argument_count; ++i)
    {
      local invalid_argument_string
      if (i == 0)
      {
         invalid_argument_string = "invalidParameter"
      }
      else
      {
        invalid_argument_string = "parameter0"
      }
      for (local j = 1; j < argument_count; ++j)
      {
        if (j == i)
        {
          invalid_argument_string = format("%s, invalidParameter", invalid_argument_string)
        }
        else
        {
          invalid_argument_string = format("%s, parameter%d", invalid_argument_string, j)
        }
      }
      test.body.add_new_line()

      switch (bind_type)
      {
      case BindTypes.kBindFunction:
        test.body.add_line("CompileAndFailCall(\"%s(%s)\");", function_name, invalid_argument_string)
        test.body.add_line("CheckErrorString(\"parameter %d has an invalid type 'OtherBoundClass' ; expected: 'BoundClass'\");", i + 1);
        break
      case BindTypes.kBindClassFunction:
        test.body.add_line("CompileAndFailCall(\"mock.Class%s(%s)\");", function_name, invalid_argument_string)
        test.body.add_line("CheckErrorString(\"parameter %d has an invalid type 'OtherBoundClass' ; expected: 'BoundClass'\");", i + 1);
        test.body.add_line("CompileAndFailCall(\"mock.ConstClass%s(%s)\");", function_name, invalid_argument_string)
        test.body.add_line("CheckErrorString(\"parameter %d has an invalid type 'OtherBoundClass' ; expected: 'BoundClass'\");", i + 1);
        break
      case BindTypes.kBindSingletonFunction:
        test.body.add_line("CompileAndFailCall(\"Mock.Class%s(%s)\");", function_name, invalid_argument_string)
        test.body.add_line("CheckErrorString(\"parameter %d has an invalid type 'OtherBoundClass' ; expected: 'BoundClass'\");", i + 1);
        test.body.add_line("CompileAndFailCall(\"ConstClass%s(%s)\");", function_name, invalid_argument_string)
        test.body.add_line("CheckErrorString(\"parameter %d has an invalid type 'OtherBoundClass' ; expected: 'BoundClass'\");", i + 1);
        break
      }
    }
  }

  file.contents.append(test)
}

// add the typedef for the fixture name
//
local typedef = CustomFileContent()
typedef.source_data = format("typedef SquirrelFixture BindFunctionTest;")
file.contents.append(typedef)

typedef = CustomFileContent()
typedef.source_data = format("typedef SquirrelFixture BindClassFunctionTest;")
file.contents.append(typedef)

typedef = CustomFileContent()
typedef.source_data = format("typedef SquirrelFixture BindSingletonFunctionTest;")
file.contents.append(typedef)

if (arguments == 0)
{
  // add the invalid test
  //
  local invalid_test = TestFixture("BindFunctionTest", "TestInvalidCall")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunction0::VoidFunction0, _SC(\"test\")));")
  invalid_test.body.add_new_line()
  invalid_test.body.add_line("sq_pushroottable(m_vm);")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunction0::VoidFunction0, nullptr));")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindFunction(m_vm, 1, &MockFunction0::VoidFunction0, _SC(\"\")));")
  invalid_test.body.add_line("sq_poptop(m_vm);")
  file.contents.append(invalid_test)

  invalid_test = TestFixture("BindClassFunctionTest", "TestInvalidCall")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunction0>(m_vm, 1, &MockFunction0::ClassVoidFunction0, _SC(\"test\")));")
  invalid_test.body.add_new_line()
  invalid_test.body.add_line("sq_newclass(m_vm, SQFalse);")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunction0>(m_vm, 1, &MockFunction0::ClassVoidFunction0, nullptr));")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindClassFunction<MockFunction0>(m_vm, 1, &MockFunction0::ClassVoidFunction0, _SC(\"\")));")
  invalid_test.body.add_line("sq_poptop(m_vm);")
  file.contents.append(invalid_test)

  invalid_test = TestFixture("BindSingletonFunctionTest", "TestInvalidCall")
  invalid_test.body.add_line("MockFunction0 mock;")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunction0::ClassVoidFunction0, _SC(\"test\")));")
  invalid_test.body.add_new_line()
  invalid_test.body.add_line("sq_newclass(m_vm, SQFalse);")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunction0::ClassVoidFunction0, nullptr));")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindSingletonFunction(m_vm, 1, &mock, &MockFunction0::ClassVoidFunction0, _SC(\"\")));")
  invalid_test.body.add_line("EXPECT_FALSE(sqb::Bind::BindSingletonFunction<MockFunction0>(m_vm, 1, nullptr, &MockFunction0::ClassVoidFunction0, _SC(\"MockFunction0\")));")
  invalid_test.body.add_line("sq_poptop(m_vm);")
  file.contents.append(invalid_test)
}

add_function_test(file, arguments, BindTypes.kBindFunction, ReturnTypes.kReturnVoid)
add_function_test(file, arguments, BindTypes.kBindClassFunction, ReturnTypes.kReturnVoid)
add_function_test(file, arguments, BindTypes.kBindSingletonFunction, ReturnTypes.kReturnVoid)

add_function_test(file, arguments, BindTypes.kBindFunction, ReturnTypes.kReturn)
add_function_test(file, arguments, BindTypes.kBindClassFunction, ReturnTypes.kReturn)
add_function_test(file, arguments, BindTypes.kBindSingletonFunction, ReturnTypes.kReturn)

add_function_test(file, arguments, BindTypes.kBindFunction, ReturnTypes.kReturnInvalid)
add_function_test(file, arguments, BindTypes.kBindClassFunction, ReturnTypes.kReturnInvalid)
add_function_test(file, arguments, BindTypes.kBindSingletonFunction, ReturnTypes.kReturnInvalid)